<template>
  <div>
    <div class="panl" @click="start" v-if="!isStart">开始</div>
    <div class="panl red" @click="stop" v-if="isStart">暂停</div>

    <!-- 波形图容器 -->
    <div class="box">
      <canvas id="spectrumCanvas" width="200" height="90"></canvas>
    </div>

    <!-- 消息列表渲染 -->
    <div id="messageList"></div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import TransformPCMWorker from './asr-pcm.worker.js?worker'
const recorderWorker = new TransformPCMWorker()
let isStart = ref(false)
let ws = null
let stream = null //麦克风
let audioContext = null //音频上下文
let analyser = null //后续做音频可视化准备
let source = null //音频源节点
let workletNode = null //对音频数据进行处理，比如录音、分析、编码、传输等
let audioFile = [] // 把每一帧的音频数据存储起来，最后可以组合成完整的录音文件。
let bufferLength = null
let dataArray = null //是一个无符号 8 位整型数组，用于保存音频的频率能量值（范围是 0~255）
let isDrawing = true // 控制波形图是否继续绘制
let barHeights = []
let buffer = []
let canvas = null
let ctx = null
const start = () => {
  isStart.value = true
  isDrawing = true
  init()
}

// ws 连接webscoket
const init = async () => {
  audioAcquisitionProcessing()

  ws = new WebSocket('ws://localhost:3002')

  // 连接建立时触发
  ws.onopen = () => {
    console.log('✅ 已连接到 WebSocket 服务器')
    ws.send('Hello Server!')
  }

  // 接收消息
  ws.onmessage = (event) => {
    console.log('📩 收到来自服务器的消息:', event.data)
    const message = event.data
    renderMessage(message)
  }

  // 错误处理
  ws.onerror = (error) => {
    console.error('⚠️ WebSocket Error:', error)
  }

  // 连接关闭
  ws.onclose = (e) => {
    console.log('🔌 连接已关闭', e)
  }
}

// ✅ 渲染消息函数
function renderMessage(message) {
  const container = document.getElementById('messageList')

  // 创建一个新的 div 元素来显示这条消息
  const msgElement = document.createElement('div')
  msgElement.textContent = message

  // 可选：加样式区分
  msgElement.style.margin = '5px 0'
  msgElement.style.padding = '10px'
  msgElement.style.borderBottom = '1px solid #eee'

  // 添加到容器中
  container.appendChild(msgElement)

  // 自动滚动到底部（方便看到最新消息）
  container.scrollTop = container.scrollHeight
}

//  音频采集 + 音频处理
async function audioAcquisitionProcessing() {
  // 使用 getUserMedia 请求用户授权访问麦克风。成功后返回一个 MediaStream 对象，表示音频输入流
  stream = await navigator.mediaDevices.getUserMedia({ audio: true })
  // console.log(stream,'stream');

  // AudioContext 是 Web Audio API 的核心，用来管理音频处理图（audio graph）。
  //所有音频节点（如分析器、处理器等）都需要在这个上下文中创建。
  audioContext = new AudioContext()

  // 加载 AudioWorklet 处理器模块  import.meta.url表示相对于当前模块路径的 processor.js 文件的完整 URL
  const processorUrl = new URL('./processor.js', import.meta.url).href
  //console.log(processorUrl,'processorUrl');//http://localhost:9527/src/view/main/ws/processor.js

  // addModule() 方法会加载这个脚本，并注册其中的处理器类（比如 "recorder-processor"）。
  console.log(audioContext.audioWorklet.addModule(processorUrl),'-------我是处理音频的主线程------');
  
  await audioContext.audioWorklet.addModule(processorUrl)

  //   AnalyserNode 可以用来实时获取音频的时域或频率数据，常用于可视化（如音柱图、频谱图）。
  // 虽然在你的代码中还没连接它，但这是为后续做音频可视化准备的。
  analyser = audioContext.createAnalyser()

  // 创建音频源节点（MediaStreamSource） 将麦克风的 MediaStream 包装成一个音频源节点，供后续音频图使用。
  source = audioContext.createMediaStreamSource(stream)

  // 根据之前加载的处理器名 "recorder-processor" 创建一个 AudioWorkletNode。
  // 这个节点会在后台线程中对音频数据进行处理，比如录音、分析、编码、传输等
  workletNode = new AudioWorkletNode(audioContext, 'recorder-processor')

  // 在 workletNode 创建后添加监听器
  workletNode.port.onmessage = (event) => {
    const audioData = event.data // Float32Array 类型，表示 PCM 音频数据
    // console.log('拿到我说的话:', audioData)

    //  检查 WebSocket 是否已连接
    if (ws.readyState !== 1) {
      return //已经连接无需处理
    }

    // 将音频数据通过 postMessage 发送给 Web Worker
    // recorderWorker 是一个 Worker 线程（Web Worker），通常用来做后台任务，比如：
    // 录音编码成 WAV 或 MP3
    // 实时语音识别
    // 音频压缩、上传等
    recorderWorker.postMessage({
      command: 'transform', //是一种约定，告诉 worker 当前要执行什么操作。
      buffer: event.data //是当前这一帧的音频数据。
    })

    console.log(audioData.buffer,'----audioData.buffer----');
    

    ws.send(audioData.buffer) // 使用 buffer 发送二进制数据

    // 把每一帧的音频数据存储起来，最后可以组合成完整的录音文件。
    // 通常在用户点击“停止”按钮后，会把这些数据合并并导出为 .wav 文件。
    audioFile.push(event.data)
  }

  //   analyser 是一个 AnalyserNode，用于音频可视化。
  // fftSize 是快速傅里叶变换的大小，决定频率分辨率。
  // 常用值有：64, 128, 256, 512, 1024, 2048 等。
  // 数值越大，波形越精细，但性能开销也越大。
  analyser.fftSize = 256

  //【必须】连接音频图，否则 processor 不会收到数据！ source 是麦克风输入源
  source.connect(workletNode) //用于自定义音频处理（如录音、传输）
  source.connect(analyser) //用于可视化（绘制波形图、频谱图）

  // 获取频率 bin 数量，并创建一个 Uint8Array 来接收数据
  bufferLength = analyser.frequencyBinCount
  dataArray = new Uint8Array(bufferLength)

  analyser.getByteFrequencyData(dataArray) // 更新 dataArray
  console.log(dataArray) // 打印当前频率能量分布

  drawWaveform()

  recorderWorker.onmessage = (e) => {
    // console.log(e, '-----recorderWorker将数据处理完音频数据后返回的结果  处理e.data.buffer成为16kHz, 16-bit PCM 的字节流	每两个字节组成一个 int16 样本的普通数组----')
  }
}

//绘制波形的,无需关心这里
function drawWaveform() {
  if (!isDrawing) return // 如果 isDrawing 为 false，不再执行绘制
  canvas = document.getElementById('spectrumCanvas')
  ctx = canvas.getContext('2d')

  requestAnimationFrame(drawWaveform)

  analyser.getByteFrequencyData(dataArray)

  const barCount = 14 // 显示14根柱子，适配小画布
  const barWidth = 4 // 柱子宽度
  const gap = 6 // 柱子间距
  const minHeight = 2 // 最低高度
  const centerY = canvas.height / 2 // 中心线

  // 初始化 heights 数组
  if (barHeights.length !== barCount) {
    barHeights = new Array(barCount).fill(minHeight)
  }

  ctx.clearRect(0, 0, canvas.width, canvas.height)

  for (let i = 0; i < barCount; i++) {
    let volume = Math.pow(dataArray[i] / 255, 2.2) // 非线性映射
    let targetHeight = Math.max(volume * canvas.height * 0.8, minHeight) // 控制最大高度不超过画布的80%

    // 平滑过渡
    barHeights[i] += (targetHeight - barHeights[i]) * 0.3

    const x =
      (canvas.width - (barCount * (barWidth + gap) - gap)) / 2 +
      i * (barWidth + gap)
    const barHeight = barHeights[i]

    // 渐变色
    const gradient = ctx.createLinearGradient(
      0,
      centerY - barHeight,
      0,
      centerY + barHeight
    )
    gradient.addColorStop(0, 'rgb(79, 151, 255)')
    gradient.addColorStop(1, 'rgb(39, 94, 255)')

    ctx.fillStyle = gradient
    ctx.fillRect(x, centerY - barHeight, barWidth, barHeight * 2)
  }
}

const stop = () => {
  isStart.value = false
  isDrawing = false // 停止绘制波形图

  // 🔥 新增：清空 canvas，恢复空白状态
  if (canvas && ctx) {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
  }

  // 可选：重置柱子高度数组
  barHeights = []
  if (ws) {
    ws.close() // 主动关闭连接
    console.log('🛑 用户关闭了 WebSocket 连接')
    ws = null
  }

  if (stream) {
    stream.getTracks().forEach((track) => track.stop()) // 停止麦克风
    stream = null
  }

  if (audioContext) {
    audioContext.close()
    audioContext = null
  }
}
</script>

<style scoped>
.panl {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 50px;
  height: 50px;
  cursor: pointer;
  background-color: skyblue;
  border-radius: 50%;
}
.red {
  background-color: brown;
  color: #fff;
}
.box {
  width: 200px;
  height: 90px;
  margin: 20px;
  border-radius: 20px;
  background-color: rgba(41, 45, 59, 0.8);
}
</style>
